/*
* udp echo server
*
* @build   make examples
* @server  bin/udp_echo_server 1234
* @client  bin/nc -u 127.0.0.1 1234
*          nc     -u 127.0.0.1 1234
*
*/

#include "hv/hloop.h"
#include "hv/hsocket.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "hv/hendian.h"
#include "hv/hlog.h"
#include <sys/types.h>
#include <sys/stat.h>
/*
* @test    kcp_server
* #define  TEST_KCP 1
*
* @build   ./configure --with-kcp && make clean && make
* @server  bin/udp_echo_server 1234
* @client  bin/nc -k 127.0.0.1 1234
*
*/
enum Block {
	_RRQ = 1,	// read request
	_WRQ = 2,	// write request
	_DATA = 3,	// data packet
	_ACK = 4,	// acknowledgement
	_ERROR = 5,	// error code
	_OACK = 6,//ѡ��Ӧ��
};

enum TftpError {
	EUNDEF		= 0,	/* not defined */
	ENOTFOUND	= 1,	/* file not found */
	EACCESS		= 2,	/* access violation */
	ENOSPACE	= 3,	/* disk full or allocation exceeded */
	EBADOP		= 4,	/* illegal TFTP operation */
	EBADID		= 5,	/* unknown transfer ID */
	EEXISTS		= 6,	/* file already exists */
	ENOUSER		= 7	/* no such user */
};

const char *errorMsg[] = {
	"Undefined error code",
	"File not found",
	"Access violation",
	"Disk full or allocation exceeded",
	"Illegal TFTP operation",
	"Unknown transfer ID",
	"File already exists",
	"No such user",
};
#define OCTET 1
#define BLOCK_SIZE 512
#define BUFFER_SIZE 65536
char file_path[256] 	= 	{ 0 };
char *ptr_filePath = file_path;
int transportType 		= 	0;
int file_size,totalSize;
int blockSize 			= 	0;
int blockIndex 			= 	1;
FILE *file				=	NULL;
int endtftp 			= 	0;
int timeout				=	0;
int retryCount			=	2;
hio_t* send_io = NULL;
hio_t* error_io = NULL;
char sendBuf[BUFFER_SIZE] = { 0 };
char readBuf[BUFFER_SIZE] = { 0 };
hloop_t* loop;
htimer_t* timer;
uint32_t getFileSize(char *path)
{
	uint32_t size = 0;
	FILE *fp = fopen(path, "rb");
	if (fp == NULL)
		return 0;
	fseek(fp, 0, SEEK_END);
	size = ftell(fp);
	return size;
}

void sendErrorCode(hio_t* io, enum TftpError code)
{
	char buf[128]={0};
	const char *msg = errorMsg[code];
	printf("<%s> %s\n", file_path,msg);
	buf[0]=0x00;
	buf[1]=0x05;
	buf[2]=0x00;
	buf[3]=(char)code;
	memcpy(&buf[4],msg,strlen(msg)+1);
	hio_write(io, buf, strlen(msg)+5);
}
void clearStatus()
{
	memset(ptr_filePath, 0, sizeof(file_path)-(ptr_filePath- file_path));
	transportType 	= 	0;
	file_size		=	0;
	blockSize 		= 	0;
	blockIndex 		= 	1;
	file			=	NULL;
	endtftp 		= 	0;
	timeout 		= 	0;
	totalSize 		= 	0;
	retryCount	 	= 	3;
}
#define progressbar 50
void printProcess(int maxValue, int curValue)
{
	char buf[256] = { 0 };
	int progress = (((float)curValue / (float)maxValue)) * 100;
	memset(buf, ' ', progressbar);
	buf[progressbar] = '\0';
	memset(buf, '*', (float)progress/100*progressbar);

	printf("<%s> |%s| [%d]\r", file_path,buf,progress);
}
void del_timer(htimer_t* tim)
{
	if(tim)
	{
		htimer_del(tim);
		retryCount=3;
	}
}
void on_timer(htimer_t* timer) {
	if (retryCount--<=0)
	{
		printf("\ntimeout\n");
		clearStatus();
		sendErrorCode(error_io, EBADOP);
		//hio_close(error_io);
		del_timer(timer);
		timer=NULL;
	}
}
void reset_timer(htimer_t* tim)
{
	if(tim)
	{
		htimer_reset(tim,0);
		retryCount=3;
	}
}
static void on_recvfrom(hio_t* io, void* buf, int readbytes) {
	uint16_t code,cmd=0;
	char *ptr = buf;
	memset(sendBuf, 0, sizeof(sendBuf));
	char *sptr = sendBuf;
	char tmp[128]={0};
	int readLen = 0;
	struct stat s_buf;
	uint16_t index = 0;
	code = ((ptr[0] >> 8) & 0xff) | (ptr[1] & 0xff);
	ptr += 2;
	retryCount = 3;
	error_io=io;
	switch (code)
	{
	case	_RRQ:
	case	_WRQ:	
	#if 0
    	if(code==_WRQ)	  
			printf("_WRQ\n");
		if(code==_RRQ)	
			printf("RRQ\n");		
	#endif	
		strncpy(ptr_filePath, ptr, sizeof(file_path)-1);
		LOGD("file name is %s\n", file_path);
		ptr += strlen(ptr_filePath) + 1;
		if (code == _WRQ)
		{
			stat(file_path,&s_buf);
			if(S_ISDIR(s_buf.st_mode))
			{
				sendErrorCode(io, EACCESS);
				clearStatus();
				break;
			}
			file = fopen(file_path, "wb+");
			if (!file)
			{
				sendErrorCode(io, ENOTFOUND);
				clearStatus();
				break;
			}
		}
		else
		{
			/*获取文件信息，把信息放到s_buf中*/
			stat(file_path,&s_buf);
			if(S_ISDIR(s_buf.st_mode))
			{
				sendErrorCode(io, ENOTFOUND);
				clearStatus();
				break;
			}
			file = fopen(file_path, "rb");
			if (!file)
			{
				sendErrorCode(io, ENOTFOUND);
				clearStatus();
				break;
			}
		}

		while(ptr-(char*)buf+1<readbytes)
		{
			if (strcmp("octet", ptr) == 0)
			{
				transportType = OCTET;
				ptr += strlen("octet") + 1;
			}
			else if (strcmp("tsize", ptr) == 0)
			{
				ptr += strlen("tsize") + 1;
				file_size = atoi(ptr);
				totalSize = file_size;
				ptr+=sprintf(tmp,"%d",file_size)+1;
			}
			else if (strcmp("timeout", ptr) == 0)
			{
				ptr += strlen("timeout") + 1;
				timeout = atoi(ptr);
				ptr += sprintf(tmp, "%d", timeout) + 1;
				printf("timeout:%d\n",timeout);
				timer = htimer_add(loop, on_timer, timeout * 1000, INFINITE);
			}
			else if (strcmp("blksize", ptr) == 0)
			{
				ptr += strlen("blksize") + 1;
				blockSize = atoi(ptr);
				ptr+=sprintf(tmp,"%d",blockSize)+1;
			}
			else
			{
				sendErrorCode(io,EUNDEF);
				if (file)
					fclose(file);
				clearStatus();
				return;
			}
		}
		if(!timeout)
		{
			timer = htimer_add(loop, on_timer,1 * 1000, INFINITE);
		}
		cmd = htobe16(_OACK);
		memcpy(sptr, &cmd, 2);
		sptr += 2;
		memcpy(sptr, "tsize", strlen("tsize"));
		sptr += strlen("tsize") + 1;
		if (code == _RRQ)
		{
			file_size = getFileSize(file_path);
			totalSize = file_size;
		}
		sptr+= sprintf(sptr, "%d", file_size);
		sptr++;
		if(timeout)
		{
			memcpy(sptr, "timeout", strlen("timeout"));
			sptr += strlen("timeout") + 1;
			sptr+= sprintf(sptr, "%d", timeout);
			sptr++;
			printf("111timeout:%d\n",timeout);
		}
		if(blockSize)
		{
			memcpy(sptr, "blksize", strlen("blksize"));
			sptr += strlen("blksize") + 1;
			sptr+= sprintf(sptr, "%d", blockSize);
			sptr++;	
		}
		else
		{
			blockSize=BLOCK_SIZE;
		}
		hio_write(io, sendBuf, sptr - sendBuf);
		break;    		// read request     
	case	_DATA:		  
		LOGD("_DATA\n");
		if (!file)
		{
			del_timer(timer);
			timer=NULL;
			sendErrorCode(io, ENOTFOUND);
			clearStatus();
			break;
		}
		if(readbytes>=blockSize)
		{
			readLen = fwrite(ptr+2,readbytes-4,1,file);
			file_size -= readbytes - 4;
		}
		else{
			readLen = fwrite(ptr+2,readbytes - 4,1,file);
			file_size -= readbytes - 4;
			fclose(file);
			del_timer(timer);
			timer=NULL;
			endtftp = 1;
		}
		printProcess(totalSize, totalSize - file_size);
		code = htobe16(_ACK);
		memcpy(sptr, &code, 2);
		sptr += 2;
		memcpy(sptr, ptr, 2);
		sptr += 2;
		hio_write(io, sendBuf, sptr - sendBuf);
		if (endtftp)
		{
			del_timer(timer);
			timer=NULL;
			printProcess(totalSize, totalSize);
			printf("\ntftp done\n");
			clearStatus();
		}
		break;				// data packet		
	case	_ACK:	
		if (endtftp)
		{
			del_timer(timer);
			timer=NULL;
			printProcess(totalSize, totalSize);
			printf("\ntftp done\n");
			clearStatus();
			break;
		}
		if (!file)
		{
			sendErrorCode(io, ENOTFOUND);
			break;
		}
			
		if (file_size < blockSize)
		{
			readLen = fread(sptr + 4, file_size, 1, file);
			readLen = file_size;
			endtftp = 1;
			fclose(file);
		}
		else
		{
			readLen = fread(sptr + 4, blockSize, 1, file);
			readLen = blockSize*readLen;
		}
		reset_timer(timer);
		file_size -= readLen;
		code = htobe16(_DATA);
		memcpy(sptr, &code, 2);
		sptr += 2;
		index = htobe16(blockIndex);
		memcpy(sptr, &index, 2);
		sptr += 2;
		blockIndex++;
		sptr += readLen;
		hio_write(io, sendBuf, sptr - sendBuf);
		printProcess(totalSize, totalSize - file_size);
		
		break;				// acknowledgement	
	case	_ERROR:	
		del_timer(timer);	
		timer=NULL;
		LOGD("_ERROR\n");
		clearStatus();
		printf("error!\r\n");
		break;				// error code		
	case	_OACK:	
		// if(timer)
		// 	htimer_del(timer);	
		LOGD("_OACK\n");
		break;
	default:
		// if(timer)
		// 	htimer_del(timer);	
		LOGD("default\n");
		break;
	}
}
const char *commond = {"\
	-p port	\n\
	-i ip		\n\
	-d dir		\n\
	-h help		\n\
	-v version  \n"
};


int main(int argc, char** argv) {
	
	char  host[32] = "0.0.0.0";
	int port = 69,tmp=0;
	for (int i = 1; i < argc;)
	{
		if (strncmp(argv[i], "-p", 2) == 0)
		{
			tmp = atoi(argv[i + 1]);
			if (tmp>0 && tmp < 65535)
			{
				port = tmp;
				i += 2;
			}
			else
			{
				printf("param error!\n");
				return 0;
			}
		}
		else if (strncmp(argv[i], "-i", 2) == 0)
		{
			strncpy(host, argv[i + 1], strlen(argv[i + 1]));
			i+=2;
		}
		else if (strncmp(argv[i], "-d", 2) == 0)
		{
			if(!argv[i + 1])
				return -EINVAL;
			strncpy(file_path, argv[i + 1], strlen(argv[i + 1]));
			if(file_path[strlen(file_path)-1]!='/')
			{
				file_path[strlen(file_path)]='/';
			}
			ptr_filePath += strlen(file_path);
			i+=2;
		}
		else if (strncmp(argv[i], "-h", 2) == 0)
		{
			printf("%s\n", commond);
			i++;
			return 0;
		}
		else if (strncmp(argv[i], "-v", 2) == 0)
		{
			i++;
			return 0;
		}
		else
			i++;
	}
	printf("start tftp server port[%d]\n",port);

	

	//hlog_set_level(LOG_LEVEL_DEBUG);

	loop = hloop_new(0);
	send_io = hloop_create_udp_server(loop, host, port);
	if (send_io == NULL) {
		printf("port occupy\n");
		hloop_free(&loop);
		return -20;
	}
	hio_set_readbuf(send_io, readBuf, BUFFER_SIZE);
	hio_setcb_read(send_io, on_recvfrom);
	hio_read(send_io);
	hloop_run(loop);
	hloop_free(&loop);
	return 0;
}
